#!/usr/bin/perl -w

use strict;
$|=1;
#------------------- global variables
my $opt_cmd = $ARGV[0];
my $opt_deamonname = scalar(@ARGV) == 2 ? $ARGV[1] : '';
my @sets = ();
my @setstrings = ();
my @deamonnames = ();
my %hash = ();
my @option = ();
my $errorinfo = '';
my $state = '';
my $localip = '';
my $localhostname = '';
my $ifconfig_str;
my @ifconfig_strs;
my @interfaces;
my $interfacename = '';

my $configfile = "/etc/memcached.cnf";

my $pkgname = "zgq_memcached_control";
#my $pidpath = $ENV{$pkgname .'__PIDPATH'};
my $pidpath = "/var/run/memcached";
if(! -d $pidpath) {
   `mkdir -p $pidpath`;
   `chown yahoo:wheel $pidpath`;
}
my @key_keys = ('p','m','l');
my %extra_keys = ('autostart'=>'1');

my $debug = 0;
if(defined($ARGV[1]) and $ARGV[1] =~ /^\-d(\d)$/) {
    $debug = $1;
    $ARGV[1] = undef;
}elsif(defined($ARGV[2]) and $ARGV[2] =~ /^\-d(\d)$/)  {
    $debug = $1;
    $ARGV[2] = undef;
}

my $exit_str = 'fail';

#------------------- function
sub print_all
{
        my ($tabnum, $h) = @_;
        (defined($h)) or return "error: input parameter is unset.\n";
        my $type = ref($h);
        if( $type eq 'ARRAY')
        {       foreach my $v (@$h) {  print_all($tabnum+1, $v); }
        }
        elsif( $type eq 'HASH')
        {       while ( my ($k, $v) = each %$h)
                {
                        foreach my $i (1 .. $tabnum) {print "\t";}
                        print "$k => ";
                        print_all($tabnum+1, $v);
                }
        }
        elsif( $type eq 'SCALAR')
        {       foreach my $i (1 .. $tabnum) {print "\t";} my $t1 = $$h; chomp($t1); printf("%s\n", $t1); }
        elsif( 'X' . $type eq 'X' )
        {       print_all($tabnum, \$h); }
        elsif( $type eq 'REF' )
        {       print_all($tabnum, $h); }
        else
        {       chomp($h);printf("warn: type is %s, %s\n",$type, $h); }
}


sub print_help
{
  printf("Usage: %s command [deamonname] [debuglevel]\n", $0);
  printf("\tcommand:
\t\tadd: initalize enviornment: attach ip
\t\tdel: anti-initalize enviornment: detach ip
\t\tstart:  start m_deamonname memcache deamon
\t\tstop:  stop m_deamonname memcache deamon
\t\tlist:  list memcache deamon
\t\tcheck:  check m_deamonname memcache deamon
\t\t(set a=datetime and get it)
\t\tdebuglevel: -d1 errorinfo; -d2 call in/return info; -d3 all data info
\tconfig set example:
zgq_memcached_control.autostart: on
zgq_memcached_control.rich-l: 192.168.100.21
zgq_memcached_control.rich-m: 2048
zgq_memcached_control.rich-p: 11212
zgq_memcached_control.xiaoer-autostart: off
zgq_memcached_control.xiaoer-c: 2048
zgq_memcached_control.xiaoer-l: 192.168.100.20
zgq_memcached_control.xiaoer-m: 1024
zgq_memcached_control.xiaoer-p: 11213
");
}

sub get_ip
{
    my $host = shift;
    my $ip = '';

    if(!defined($host) or !$host) { 
        $host = $localhostname;
    }

    printf("sub get_ip host = $host\n") if($debug>=3);
    my ($name, $aliases, $addrtype, $length, @addrs) = gethostbyname($host);
    my @ips = ();
    foreach my $i (@addrs) {
        @ips = unpack('C4',$i);
    }
    foreach my $i (@ips) {
        $ip .= sprintf("%d", $i) .'.';        
    }
    printf("sub get_ip ip = $ip\n") if($debug>=3);
    if($ip) {
        ($ip) = ($ip =~ /^(.*?)\.$/);
        printf("sub get_ip: host %s, ip %s\n", $host, $ip) if($debug >= 3);
        return $ip;
    } else {
        printf("ERROR: get ip from hostname $host failed.\n");
        return '';
    }
}

sub get_ip_bak
{
    my $host = shift;
    if(!defined($host) or !$host) {
        $host = `host`;
    }

	my $ip = `host $host|cut -d' ' -f4`;
	chomp($ip);
    printf("sub get_ip: host %s, ip %s\n", $host, $ip) if($debug>=2);
	return $ip;
}

sub add_deamon
{
    my $deamon = shift;
    return &attach_ip($interfacename, $hash{$deamon}{'l'});
}

sub del_deamon
{
    my $deamon = shift;
    return &detach_ip($interfacename, $hash{$deamon}{'l'});
}

sub add_deamon_all
{
    foreach my $d (@deamonnames) {
        return 0 if(! &add_deamon($d))
    }
    return 1;
}

sub del_deamon_all
{
    foreach my $d (@deamonnames) {
        return 0 if(! &del_deamon($d))
    }
    return 1;
}

sub sets_format
{

    my $flag = 'success';

	foreach my $set (@setstrings) {
		#zgq_memcached_control.deamonname-name: value
   		#zgq_memcached_control.profile-ip: 192.168.1.22	
   		
   		chomp($set);
   		if($set =~ /^$pkgname\.(\S+)-(\S+):\s(\S*)$/) {
			#hash{deamonname}{key} = value;
   			$hash{$1}{$2} = $3;
   		} else {
   			 #die("what happen? line is wrong. $set .\n");
   		}
	}

    my $ip;
    my $d;
    #check key setting
    foreach $d (keys(%hash)) {
       if(defined($hash{$d}{'l'}) and $hash{$d}{'l'}) {
                $ip = &get_ip($hash{$d}{'l'});
                if($ip) {
                    $hash{$d}{'l'} = $ip;
                } else {
                    printf("ERROR: deamon name %s get ip from host %s failed.\n", $d, $hash{$d}{'l'});
                    $flag = 'fail';
                }
        } else {
               #take as use default ip
               $ip = &get_ip();
               $hash{$d}{'l'} = $ip;
        }

        if(!defined($hash{$d}{'p'})) {
            $hash{$d}{'p'} = 11211;
        }
    }
    &print_all(\%hash) if($debug>=3);
    return 0 if($flag eq 'fail');

    #check conflict
    my %ip_ports = ();
    foreach $d (keys(%hash)) {
        my $k = $hash{$d}{'l'} .':'. $hash{$d}{'p'};
        if(defined($ip_ports{$k}) and $ip_ports{$k}) {
            printf("ERROR: deamon %s's ip port is conflicting with deamon %s , all are %s.\n", $d, $ip_ports{$k}, $k);
            $flag = 'fail';
        }

        if($hash{$d}{'p'} == 11211 and ! `cat $configfile|egrep -e '(\-l|autostart: off)'`) {
            printf("ERROR: the memcached default deamon working on 11211, you must do:\nset default deamon -l to sepicalip %s\n", $localip);
            $flag = 'fail';
        }
        $ip_ports{$hash{$d}{'l'} .':'. $hash{$d}{'p'}} = $d;
    }
    
    if($debug >=3) {
        printf("config get from $configfile:\n");
        &print_all(1,\%hash) 
    }
    return $flag eq 'fail' ? 0 : 1;
}

sub check_format
{
    my $deamonname = shift;
    return 1 if(!defined($deamonname) or !$deamonname);

    foreach my $k (@key_keys) {
        if(!defined($hash{$deamonname}{$k}) or !defined($hash{$deamonname}{$k})) {
            printf("ERROR: deamon name %s missing set %s\n", $deamonname, $k);
            return 0 
        }
    }
    return 1;
}

sub get_sets
{
	my $format = shift; #list/option/deamonname
	my $argu_deamonname = shift;
	my $ret = '';

    my $key;
	foreach my $deamonname (keys(%hash)) {
		next if($argu_deamonname and $argu_deamonname ne $deamonname);

        my $ta;
		if($format eq 'list') {
            $ta = $hash{$deamonname};
			foreach $key (keys(%$ta)) {
				$ret .= $pkgname .'.'. $deamonname .'-'. $key .': '. $hash{$deamonname}{$key} ."\n";
			}
		} elsif($format eq 'option') {
            $ta = $hash{$deamonname};
			foreach $key (keys(%$ta)) {
                next if(defined($extra_keys{$key}));
				$ret .= ' -'. $key;
                $ret .= ' '. $hash{$deamonname}{$key} .' ' if(defined($hash{$deamonname}{$key}) and $hash{$deamonname}{$key});
			}
		} elsif($format eq 'deamonname') {
				$ret .= $deamonname ."\n";
		} else {
			die("I confused, please help me.\n");
   		}
   	}
    printf("sub get_sets: ret = %s\n", $ret) if($debug >= 2);
	return $ret;
}

sub detach_ip
{
    my $interface = shift;
    my $ip = shift;

    return 1 if($ip eq $localip);
    my $fn = `grep '^IPADDR="$ip"' /etc/sysconfig/network-scripts/ifcfg-$interfacename*|cut -d':' -f2`;
    chomp($fn);
	return 1 if(!$fn);

    `/sbin/ifdown $interfacename:$fn && unlink /etc/sysconfig/network-scripts/ifcfg-$interfacename:$fn`;

	if($?) {
		printf("detach ip $ip failed.\n");
        return 0;
    } else {
		#printf("detach ip $ip successfully.\n");
        return 1;
	}
}

sub attach_ip
{
    my $interface = shift;
    my $ip = shift;

    return 1 if($ip eq $localip);
	if(`grep $ip /etc/sysconfig/network-scripts/ifcfg-$interfacename*`) {
		#printf("already binded $ip in /etc/sysconfig/network-scripts/ifcfg-$interfacename* .\n");
		return 1;
	}

	my $next = 0; my $max = 10;
    for($next=1;$next<$max;$next++) {
        last if(! -f "/etc/sysconfig/network-scripts/ifcfg-$interfacename:$next");
    }
    if($next == $max) {
    	$max = `ls /etc/sysconfig/network-scripts/ifcfg-$interfacename:*|cut -d':' -f2|sort -n|tail -1`;
        chomp($max);
    	if($max =~ /^\d+$/) { $next = sprintf("%s", $max + 1); } #else { $next = '11'; }
        else { printf("ERROR:bind ip $ip failed due to can't find avail interface.\n"); return 0; }
    }
	
	`cat > /etc/sysconfig/network-scripts/ifcfg-$interfacename:$next <<ENDL
DEVICE="eth0:$next"
IPADDR="$ip"
BROADCAST="0.0.0.0"
NETMASK="255.255.255.255"
ONBOOT="yes"
ENDL`;
    #`/etc/init.d/network restart`;
    `/sbin/ifup $interfacename:$next`;

	if($?) {
		printf("ERROR:bind ip $ip failed.\n");
        return 0;
    } else {
		#printf("bind ip $ip successfully.\n");
        return 1;
	}
}


sub get_interface_by_deamon
{
    my $deamon = shift;

    my $ip = $hash{$deamon}{'l'};
    foreach my $i (@interfaces) {
        return $i if(`ifconfig $i|grep $ip`); 
    }
    return '';
}

#working -> not_working -> not_running => messing
sub check_state
{
	my $deamonname = shift;
		
	#running|notrunning: ps aux|grep mem, 
	my $ip = $hash{$deamonname}{'l'}; 
	my $port = $hash{$deamonname}{'p'}; 
	
    printf("sub check_state: deamonname = %s, ip = %s, port = %s\n", $deamonname, $ip, $port);
    my $cmd = '';
    my $ret = '';
    
    my $interface = &get_interface_by_deamon($deamonname);

    $cmd = "ifconfig $interface|grep UP";
    $ret = `$cmd`;
    if($debug >= 3) {
        printf("sub check_state: deamon name %s, cmd = %s\n\tret = %s\n",$deamonname, $cmd, $ret);
    }

	if($ip and ! $ret) {
		printf("ip $ip isn't binded to interface or not UP.that should be configed during add_deamon period.\n");
		return 'messing';
	}
	
	my $pidfilename = $deamonname .'_'. $hash{$deamonname}{'p'} .'pid';
		
	$cmd = 'ps aux|grep memcached|grep -v grep';
	$cmd .= "|grep $ip" if($ip);
	$cmd .= "|grep $port" if($port);
	
	$ret = `$cmd`;
    printf("sub check_state: cmd = %s, ret = %s, exit code = %s\n", $cmd, $ret, $?) if($debug >=3);

	if(!$ret) {
		printf("not running by check with cmd %s.\n", $cmd, $ret);
		return 'not_running';
	}
	
	if($ret =~ /-P (\S+)/ and ! -e $1) {
		printf("pidfile %s isn't existing.\n", $1);
		return 'messing';
	}
	
    `(echo quit;)|nc $ip $port`;
	if($?) {
		printf("memcached on $ip $port, it is running, but not work.\n");
		return 'not_woring';
	}
	
	return 'working';
	#working: telnet port , get ,put , working
	
	#messing: pid file
}

sub start
{
	my $deamonname = shift;

	my $option = &get_sets('option', $deamonname);
	if(!$option) {
		$errorinfo .= "option $option is wrong.\n";
		return 'fail';	
	}
	
    	my $cmd = ''; my $ret = '';

	$cmd="/bin/yahoo-mode-choice -b memcached";
	my $path=`$cmd`;
    printf("sub start: cmd = %s, ret = %s, exit code = %s\n", $cmd, $path, $?) if($debug >=3);
	if(!$path) {
		$errorinfo .= "/bin/yahoo-mode-choice -b memcached get failed.\n";
		return 'fail';
	}

	my $pidfilename = $deamonname .'_'. $hash{$deamonname}{'p'} .'.pid';
	if(-f $pidfilename) {
		printf("WARN: %s pidfile %s existing before start, delete it.\n", $deamonname, $pidfilename);
		unlink $pidfilename;
	}
	
    $cmd = "$path/memcached -d -u yahoo $option -P $pidpath/$pidfilename";
    $ret = `$cmd`;
    printf("sub start: cmd = %s, ret = %s, exit code = %s\n", $cmd, $ret, $?) if($debug >= 3);
	if($?) {
		$errorinfo .= "execute $path/memcached $option failed. exit code is $? .\n";
		return 'fail';
	}
	return 'success';
}

sub checkstart()
{
	my $deamonname = shift;
	

	my $state = &check_state($deamonname);
	if($state eq 'not_working') {
		printf("running but don't work,please stop it by manully.\n");
		return 'fail';
	} elsif($state eq 'not_running') {
		if(&start($deamonname) eq 'success' and &check_state($deamonname) eq 'working') {
			printf("memcahed deamon %s start successfully.\n", $deamonname);
			return 'success';
		} else {
			printf("memcahed deamon %s start failed.\n", $deamonname);
			return 'fail';
		}
	} elsif($state eq 'working') {
		printf("memcahed deamon %s already started and working.\n", $deamonname);
		return 'success';
	} elsif($state eq 'messing') {
		printf("memcahed deamon %s messing,please check it manually.\n", $deamonname);
		return 'fail';
	} else {
		die("help me, where im I?\n");
	}
}

sub stop
{
	my $deamonname = shift;
	
	my $pidfilename = $deamonname .'_'. $hash{$deamonname}{'p'} .'.pid';
	
	my $pid = 0;
	
    printf("sub stop:going to stop memcached pid file = %s\n", "$pidpath/$pidfilename");
	if(-f "$pidpath/$pidfilename") {
        my $pid = `cat $pidpath/$pidfilename`;
        chomp($pid);
        if($pid and $pid =~/^\d+$/) {
           `kill $pid`;
            if(!$?) {
                unlink "$pidpath/$pidfilename";
                printf("stoped $deamonname successfully.\n");
        		return 'success';
            }else{
                printf("stoped $deamonname failed.please try to kill -9 $pid to stop it.\n");
                return 'fail';
            }
        }else{
            printf("stoped $deamonname failed. pidfile $pidpath/$pidfilename isnot available.pid %s illegal\n", $pid);
            return 'fail';
       }
	} else {
		printf("deamonname $deamonname pidfile $pidfilename isnot existing.\n");
		my $cmd = "ps aux|grep memcached|grep -v grep";
		$cmd .= "|grep " . $hash{$deamonname}{'l'} if($hash{$deamonname}{'l'});
		$cmd .= "|grep " . $hash{$deamonname}{'p'} if($hash{$deamonname}{'p'});
		my $process_info = `$cmd`;
		$cmd .= "|awk '{ print \$2; }'";
		$pid = `$cmd`;
		if($pid) {
			printf("WARN:processinfo: %s\n", $process_info);
			printf("kill again: kill $pid.\n");
            `kill -9 $pid`;
            if(!$?) {
    			unlink  "$pidpath/$pidfilename";
                printf("stoped $deamonname successfully.\n");
        		return 'success';
            }
		} else {
            printf("ps to grep to get pid failed for deamonname $deamonname, please stop it by youself manually.\n");
            return 'fail';
        }
	}
	
	if(&check_state($deamonname) eq 'not_running') {
		return 'success';
	} else {
		return 'fail';
    }
}

sub checkstop()
{
	my $deamonname = shift;

	my $state = &check_state($deamonname);
	
	if($state eq 'not_running') {
		return 'success';
	} elsif($state eq 'not_working' or $state eq 'working') {
		if(&stop($deamonname) eq 'success' and &check_state($deamonname) eq 'not_running') {
			printf("memcahed deamon %s stoped successfully.\n", $deamonname);
			return 'success';
		} else {
			printf("memcahed deamon %s stoped failed.\n", $deamonname);
			return 'fail';		
		}
	} elsif($state eq 'messing') {
		printf("memcahed deamon %s messing,please check it manually.\n", $deamonname);
		return 'fail';
	} else {
		die("help me, where im I?\n");
	}
}

#------------------------------------------------------------------- main

if(scalar(@ARGV) > 3 or scalar(@ARGV) == 0) {
  printf("ERROR:option parameter is wrong.\n");
  print_help();
  exit 1;
}

$opt_cmd = $ARGV[0] ? $ARGV[0] : '';
$opt_deamonname = (scalar(@ARGV) == 2) ? $ARGV[1] : '';

if($opt_cmd eq 'start' or $opt_cmd eq 'stop') {
	#avoid to restart deamon during install 
	my $ret = `ps aux|grep 'ssh'|grep zgq_memcached_control|grep -v grep`;
	if(defined($ret) and $ret and $ret > 0) {
		printf("don't restart memcached to avoid missing cache data.\n"); # if($debug >=2);
		exit(0);
	}
}


$localhostname = `hostname`;
chomp($localhostname);
$localip = &get_ip();
my $i;
foreach $i(@interfaces) {  if(`ifconfig $i |grep $localip`) { $interfacename = $i; last;} }

@setstrings = `cat $configfile`;

#setstrings map to %hash
if(! &sets_format() ) {
    printf("paramter format error.\n");
    print_help();
    exit 1;
}

#some key must be set
&check_format();

@deamonnames = keys(%hash);
if($debug>=2) { printf("deamon name list:\n"); foreach my $k (@deamonnames) {  printf("\t%s\n", $k);}};
if(defined($opt_deamonname) and $opt_deamonname and !defined($hash{$opt_deamonname})) {
    printf("ERROR: deamonname %s don't exist\n", $opt_deamonname);
    exit(1);
}

#&fix_original_deamon('fix');

$ifconfig_str = `ifconfig`;
@ifconfig_strs = split(/\n/, $ifconfig_str);
foreach $i (@ifconfig_strs) { push(@interfaces,$1) if($i =~ /^(\S+)/); }
if($debug>=3) { printf("network interface:\n"); foreach $i (@interfaces) { printf("\t%s\n", $i); } }


my $deamonname = '';
if($opt_cmd eq 'add') {
	if($opt_deamonname) {
        $exit_str = 'success' if(&add_deamon($opt_deamonname) and  &checkstart($opt_deamonname));
    } else {
		foreach $deamonname (@deamonnames) {
            if(&add_deamon($deamonname) and &checkstart($deamonname)) {
                $exit_str = 'success';
            } else {
                last;
            }
        }
    }
} elsif($opt_cmd eq 'del') {
	if($opt_deamonname) {
         $exit_str = 'success' if(&checkstop($opt_deamonname) and &del_deamon($opt_deamonname));
    } else {
		foreach $deamonname (@deamonnames) {
            if(&checkstop($deamonname) and &del_deamon($deamonname)) {
                $exit_str = 'success';
            } else {
                last;
            }
        }
    }
} elsif($opt_cmd eq 'start') {
	if($opt_deamonname) {
		$exit_str = &checkstart($opt_deamonname);
	} else {
		foreach $deamonname (@deamonnames) {
            next if(defined($hash{$deamonname}{'autostart'}) and $hash{$deamonname}{'autostart'} eq 'off');
			$exit_str = &checkstart($deamonname);
			last if($exit_str eq 'fail');
		}
	}
} elsif($opt_cmd eq 'stop') {
	if($opt_deamonname) {
		$exit_str = &checkstop($opt_deamonname);
	} else {
		foreach $deamonname (@deamonnames) {
            next if(defined($hash{$deamonname}{'autostart'}) and $hash{$deamonname}{'autostart'} eq 'off');
			$exit_str = &checkstop($deamonname);
            last if($exit_str eq 'fail');
		}
	}
} elsif($opt_cmd eq 'check') {
	if($opt_deamonname) {
		printf("memcahed deamaon %s state is %s\n", $opt_deamonname, &check_state($opt_deamonname));
		exit 0;
	} else {
		foreach $deamonname (@deamonnames) {
			printf("memcahed deamaon %s state is %s\n", $deamonname, &check_state($deamonname));
		}
		exit 0;
	}
} elsif($opt_cmd eq 'list') {
	printf("memcached deamon setting: \n");
	printf("zgq_memcached_control.deamonname-key: value\n");
	printf("====================.==========-===: =====\n");
	printf("%s\n", &get_sets('list'));
	exit 0;
} else {
  printf("ERROR:command is wrong\n");
  print_help();
  exit 2;
}

printf("ERROR:%s\n", $errorinfo) if($errorinfo);
if($exit_str eq 'fail') {
	exit 1;
} elsif($exit_str eq 'success') {
	exit 0;
} else {
	exit 2;
}
